#include "engine.h"

#include "system/config.h"
#include "User.h"
#include "constant.h"
#include "Proxy.h"

#include "NResource/Static.h"
#include "NLogic/Item.h"
#include "NLogic/Static.h"
#include "NState/Static.h"

#include "Session.h"
#include "GlobalChecker.h"

#include "Encryption.h"

#include <sstream>
#include <mutex>
#include <condition_variable>

#include "Enum.h"
#include "Config.h"

#include "helper.h"

#include "Encryption.h"

#include <json/json.h>
#include <curl/curl.h>

#include "Static.h"

#include <typeinfo>
#include <boost/algorithm/string.hpp>

#include<iostream>
#include<ctime>

#include <Network/Parser.h>
#include <Network/Session.h>


#include <gtest/gtest.h>
#include "TDD/SafeArrayTest.h"

std::condition_variable condition_;

#define ERR_ASSERT(x)		((void)x)

void sigExitHandler(int sig)
{
	::Logger::GETLOG()->ToAll(::Logger::OBSERVER, "%s sig : %d", __FUNCTION__, sig);
	condition_.notify_one();
}

template <typename T> void Close_ptr(T& t) { t->Close(); }
void setSignal()
{
	struct sigaction sa;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sa.sa_handler = sigExitHandler;
	sigaction(SIGHUP, &sa, NULL);
	sigaction(SIGTERM, &sa, NULL);
	sigaction(SIGINT, &sa, NULL);

	// ignore SIGPIPE
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sa.sa_handler = SIG_IGN;
	sigaction(SIGPIPE, &sa, NULL);
}

bool Init_Database()
{
	::Database::Session::Argument argument;
	argument.nDatabaseType = 1;
	argument.nCount = 1;
	argument.strIP = ::ProjectZ::Config::Instance().GetResourceFile().host;
	argument.strID = ::ProjectZ::Config::Instance().GetResourceFile().user;
	argument.strPass = ::ProjectZ::Config::Instance().GetResourceFile().password;
	argument.strDatabase = ::ProjectZ::Config::Instance().GetResourceFile().name;

	bool bRet = ::Database::GETSESSION()->Open(argument);
	if (false == bRet)
	{
		DEBUGLOG("DATABASE OPEN FAIL");
		return false;
	}
	else
	{
		DEBUGLOG("DATABASE CONNECTED - %s:%s, ID: %s", argument.strDatabase.c_str(), argument.strIP.c_str(), argument.strID.c_str());
	}

	return true;
}

bool resource_database()
{
	::Database::Command::Auto clsAuto(::Database::GETCOMMANDPOOL());
	clsAuto->SetQueryType(::Database::Command::SELECT);
	clsAuto->MakeQuery("SELECT * FROM env_database WHERE active = 1");
	if (false == ::Database::GETSESSION()->SyncExecute(static_cast<int>(DatabaseType::GAME_MASTER), clsAuto))
		return false;

	if( clsAuto->GetErrorIdx() != 0 )
		return false;

	if (0 == clsAuto->ResultRowCount())
		return false;

	int seq = 0;
	int active = 0;
	int type = 0;
	::Toolkit::Str<20> host;
	::Toolkit::Str<20> user;
	::Toolkit::Str<20> pass;
	::Toolkit::Str<20> database;
	::Toolkit::Str<20> desc;

	bool bRet = false;
	while( clsAuto->Next() )
	{
		clsAuto->GetRecord() >> seq >> active >> type >> host >> user >> pass >> database >> desc;

		::Database::Session::Argument arg;
		arg.nDatabaseType = type;
		arg.nCount = 1;
		arg.strIP = host.c_str();
		arg.strID = user.c_str();
		arg.strPass = pass.c_str();
		arg.strDatabase = database.c_str();

		bRet = ::Database::GETSESSION()->Open(arg);
		if (false == bRet)
		{
			DEBUGLOG("%s DATABASE OPEN FAIL", desc.c_str());
			return false;
		}
		else
		{
			DEBUGLOG("[%s] DATABASE CONNECTED - %s:%s, ID: %s", desc.c_str(), arg.strDatabase.c_str(), arg.strIP.c_str(), arg.strID.c_str());
		}
	}

	return true;
}
bool init_Validator()
{
	::Validator::Session::Argument validatorArg;

	validatorArg.nCount = 1;
	validatorArg.strIOSURL = "https://buy.itunes.apple.com/verifyReceipt";
	validatorArg.strIOSURL_TEST = "https://sandbox.itunes.apple.com/verifyReceipt";
	validatorArg.strGoogleURL = ::ProjectZ::Config::Instance().GetResourceDatabase()._google_validate_url.c_str();
	validatorArg.strPackageName = ::ProjectZ::Config::Instance().GetResourceDatabase()._packageName.c_str();
	validatorArg.strPublicKey = ::ProjectZ::Config::Instance().GetResourceDatabase()._publicKey.c_str();

	return ::Validator::GETSESSION()->Open(validatorArg);
}

bool init_Memcache()
{
	::Database::Command::Auto clsAuto(::Database::GETCOMMANDPOOL());
	clsAuto->SetQueryType(::Database::Command::SELECT);
	clsAuto->MakeQuery("SELECT * FROM env_memcache WHERE active = 1");
	if (false == ::Database::GETSESSION()->SyncExecute(static_cast<int>(DatabaseType::GAME_MASTER), clsAuto))
		return false;

	if( clsAuto->GetErrorIdx() != 0 )
		return false;

	if (0 == clsAuto->ResultRowCount())
		return false;

	int seq;
	int active;

	::Toolkit::Str<20> ip;
	::Toolkit::Str<20> port;

	int count = 0;
	std::stringstream value;
	while( clsAuto->Next() )
	{
		if (0 < count) { value << ","; }
		clsAuto->GetRecord() >> seq >> active >> ip >> port;

		value << ip.c_str();
		value << ":";
		value << port.c_str();

		count++;
	}
	DEBUGLOG("init_Memcache Load Server Info: %s", value.str().c_str());

	::ProjectZ::NLogic::Memcached * clsMemcached = ::ProjectZ::NLogic::Static::Instance().GETMEMCACHED();
	if( !clsMemcached->Init(value.str().c_str()) ) { DEBUGLOG("memcached error"); return false; }

	return true;
}

bool init_MongoDB()
{
	::Database::Command::Auto clsAuto(::Database::GETCOMMANDPOOL());
	clsAuto->SetQueryType(::Database::Command::SELECT);
	clsAuto->MakeQuery("SELECT * FROM env_mongodb WHERE active = 1");
	if (false == ::Database::GETSESSION()->SyncExecute(static_cast<int>(DatabaseType::GAME_MASTER), clsAuto))
		return false;

	if( clsAuto->GetErrorIdx() != 0 )
		return false;

	if (0 == clsAuto->ResultRowCount())
		return false;

	int seq;
	int active;

	::Toolkit::Str<20> ip;
	int port = 0;

	while( clsAuto->Next() )
	{
		clsAuto->GetRecord() >> seq >> active >> ip >> port;
	}
	DEBUGLOG("init_MongoDB Load Server Info: ip=%s, port=%d", ip.c_str(), port);

	::ProjectZ::NGameLog::MongoDB * pMongoDB = ::ProjectZ::NGameLog::Static::Instance().GetMongoDB();
	pMongoDB->Init(ip.c_str(), port);

	return true;
}

bool init_Redis()
{
	::Database::Command::Auto clsAuto(::Database::GETCOMMANDPOOL());
	clsAuto->SetQueryType(::Database::Command::SELECT);
	clsAuto->MakeQuery("SELECT * FROM env_redis WHERE active = 1");
	if (false == ::Database::GETSESSION()->SyncExecute(static_cast<int>(DatabaseType::GAME_MASTER), clsAuto))
		return false;

	if( clsAuto->GetErrorIdx() != 0 )
		return false;

	if (0 == clsAuto->ResultRowCount())
		return false;

	int seq;
	int active;
	int serverType;
	int port = 0;
	::Toolkit::Str<20> ip;
	::Toolkit::Str<50> desc;
	::Toolkit::Str<30> authKey;

	while( clsAuto->Next() )
	{
		clsAuto->GetRecord() >> seq >> active >> serverType >> ip >> port >> authKey >> desc;

		::NoSQL::Session::Info clsInfo;
		clsInfo._eServer		= ::NoSQL::Session::Info::REDIS;
		clsInfo._nServerType	= serverType;
		clsInfo.ip				= ip.c_str();
		clsInfo.port			= port;
		clsInfo.authKey			= authKey.c_str();

		boost::trim(clsInfo.authKey);

		if (false == ::NoSQL::SESSION()->RegistServer(1, clsInfo))
		{
			DEBUGLOG("false == ::NoSQL::SESSION()->RegistServer(1, clsInfo)");
			return false;
		}
		else
		{
			DEBUGLOG("[%s] CONNECTED REDIS TYPE=%d, IP=%s, PORT=%d", desc.c_str(), serverType, ip.c_str(), port);
		}

	}

	return true;
}

bool Init_ServerAddr()
{
	::Database::Command::Auto clsAuto(::Database::GETCOMMANDPOOL());
	clsAuto->SetQueryType(::Database::Command::S_PROCEDURE);
	clsAuto->MakeQuery("call sp_insert_serveraddr_ccu(%d, '%s', %d)",
			::ProjectZ::Config::Instance().GetResourceDatabase()._channelUniqueIndex,
			::ProjectZ::Config::Instance().GetResourceDatabase()._service_url.c_str(),
			::ProjectZ::Config::Instance().GetResourceDatabase()._port );
	if (false == ::Database::GETSESSION()->SyncExecute(static_cast<int>(DatabaseType::LOG_MASTER), clsAuto))
		return false;

	if( clsAuto->GetErrorIdx() != 0 )
		return false;

	clsAuto->SetQueryType(::Database::Command::S_PROCEDURE);
	clsAuto->MakeQuery("call sp_serveraddr_ccu()");
	if (false == ::Database::GETSESSION()->SyncExecute(static_cast<int>(DatabaseType::LOG_MASTER), clsAuto))
		return false;

	if( clsAuto->GetErrorIdx() != 0 )
		return false;

	if (0 == clsAuto->ResultRowCount())
		return false;

	::Toolkit::Str<20> ip;

	::ProjectZ::NLogic::ServerAddrCCU * clssvrAddr = ::ProjectZ::NLogic::Static::Instance().GETSERVERADDRCCU();
	auto & svrAddrTree = clssvrAddr->GetServerAddrTree();
	while( clsAuto->Next() )
	{
		::ProjectZ::NLogic::ServerAddrCCU::Data data;
		clsAuto->GetRecord() >> data._serverKey >> data._domainName >> data._port >> data._isActive >> data._ccu
				>> data._ch1 >> data._ch2 >> data._ch3 >> data._ch4 >> data._ch5
				>> data._ch6 >> data._ch7 >> data._ch8 >> data._ch9 >> data._ch10
				>> data._ch11 >> data._ch12 >> data._ch13 >> data._ch14 >> data._ch15
				>> data._ch16 >> data._ch17 >> data._ch18 >> data._ch19 >> data._ch20
				>> data._timeStamp;

		svrAddrTree[data._serverKey] = data;
	}

	return true;
}

bool End_ServerAddr()
{
	::Database::Command::Auto clsAuto(::Database::GETCOMMANDPOOL());
	clsAuto->SetQueryType(::Database::Command::S_PROCEDURE);
	clsAuto->MakeQuery("call sp_end_serveraddr_ccu(%d)",
			::ProjectZ::Config::Instance().GetResourceDatabase()._channelUniqueIndex);
	if (false == ::Database::GETSESSION()->SyncExecute(static_cast<int>(DatabaseType::LOG_MASTER), clsAuto))
		return false;

	if( clsAuto->GetErrorIdx() != 0 )
		return false;

	return true;
}

int GetMiddleWareCount(int type)
{
	::Database::Command::Auto clsAuto(::Database::GETCOMMANDPOOL());
	clsAuto->SetQueryType(::Database::Command::SELECT);
	clsAuto->MakeQuery("SELECT count(*) FROM %s WHERE type = %d and active = 1",
			::ProjectZ::Config::Instance().GetResourceDatabase()._resource_proxy.c_str(), type);
	if (false == ::Database::GETSESSION()->SyncExecute(static_cast<int>(DatabaseType::GAME_MASTER), clsAuto))
		return false;

	if( clsAuto->GetErrorIdx() != 0 )
		return false;

	if (0 == clsAuto->ResultRowCount())
		return false;

	int count = 0;

	while( clsAuto->Next() )
	{
		clsAuto->GetRecord() >> count;
	}

	return count;
}


class LocalParser : public ::Network::Parser
{
public:
	LocalParser(::Network::Session * pSession) : ::Network::Parser(pSession){}
	~LocalParser() {}

public:
	virtual void 		OnRead(int readSize, char * pSource)
	{
	}

	virtual void 		OnComplete(::Network::Packet * pPacket)
	{
	}
};

int main(int argc, char **argv)
{
	::testing::InitGoogleTest(&argc, argv);
	int nRetGoogleTest = RUN_ALL_TESTS();
	std::cout << "RUN_ALL_TESTS return value :" << nRetGoogleTest << "\n";

	curl_global_init(CURL_GLOBAL_ALL);

	bool bReady = true;

	std::mutex mutex_;
	setSignal();
	srand((unsigned)time(NULL));

	::Logger::Static logger;
	logger.Init();

	logger.SetMaxLogSize(1024 * 1000 * 1000);

	::Dispatcher::Static dispatcher;
	dispatcher.Init();

	::Validator::Static validator;
	validator.Init();

	::Timer::Static clsTimer;
	clsTimer.Init();

	::Database::Static clsDatabase;
	clsDatabase.Init();

	::NoSQL::Static clsNoSQL;
	clsNoSQL.Init();

	::Network::Static clsNetwork;

	std::vector<ProjectZ::Session*> sessionList;

	{
		::ProjectZ::Config clsConfig;
		::ProjectZ::Static				clsProjectZStatic;


		if (false == clsConfig.LoadFile()) { PROCESSLOG("clsConfig.LoadFile() FAIL"); bReady = false; }

		if (false == Init_Database()) { PROCESSLOG("Init_Database FAIL"); bReady = false; }
		if (false == resource_database()) { PROCESSLOG("resource_database FAIL"); bReady = false; }

		if (false == clsConfig.LoadDatabase()) { PROCESSLOG("clsConfig.LoadDatabase() FAIL"); bReady = false; }


		if (true == ::ProjectZ::Config::Instance().GetResourceDatabase()._isTest)
		{
			clsNetwork.Init(4, 1024, 1024);
		}
		else
		{
			clsNetwork.Init(4, 1024 * 10, 1024 * 30);
		}

		clsNetwork.SetListener(::Dispatcher::GETHANDLER());
		clsNetwork.SetServerListener(::Dispatcher::GETHANDLER());
		clsTimer.SetListener(::Dispatcher::GETHANDLER());
		clsDatabase.SetListener(::Dispatcher::GETHANDLER());


		if (false == init_Validator()) { PROCESSLOG("init_Validator FAIL"); bReady = false; }
		if( false == ::ProjectZ::NProxy::GetConnectionInfoFromDB()) { PROCESSLOG("GetConnectionInfoFromDB error"); bReady = false; }

		::ProjectZ::NState::Static 		clsStateStatic;
		::ProjectZ::User::Factory 		clsUserFactory;
		::ProjectZ::NLogic::Static		clsLogicStatic;
		::ProjectZ::GlobalChecker		clsGlobalCheker;
		::ProjectZ::NGameLog::Static	clsGameLogStatic;

		::ProjectZ::NResource::Static clsResourceStatic;
		if (false == clsResourceStatic.LoadResource(::ProjectZ::NResource::Static::E_TYPE::_ALL_))
		{
			PROCESSLOG("LoadResource Load Fail");
			bReady = false;
		}

		if ( false == init_Memcache() )
		{
			PROCESSLOG("init_Memcache Fail");
			bReady = false;
		}

		if ( false == init_MongoDB() )
		{
			PROCESSLOG("init_MongoDB Fail");
			bReady = false;
		}

		if ( false == init_Redis() )
		{
			PROCESSLOG("init_Redis Fail");
			bReady = false;
		}

		if( !Init_ServerAddr() )
		{
			PROCESSLOG("Init_ServerAddr Fail");
			bReady = false;
		}

		::ProjectZ::Encryption clsEncryption;
		clsEncryption.MakeKey();

		const int MAX_SESSION_POOL = ProjectZ::Config::Instance().GetResourceDatabase()._sessionPool;
		PROCESSLOG("server session pool = %d", MAX_SESSION_POOL);

		int maxConnectUserCountInServer = ::ProjectZ::Config::Instance().GetResourceDatabase()._maxChannel * ::ProjectZ::Config::Instance().GetResourceDatabase()._maxChannelUser;
		::ProjectZ::Static::Instance().SetMaxConnectUserCountInServer(maxConnectUserCountInServer);


		int nCacheCount = GetMiddleWareCount(0);
		int nLocationCount = GetMiddleWareCount(1);
		int nFriendsCount = GetMiddleWareCount(2);
		int nGuildCount = GetMiddleWareCount(3);

		if (0 >= nCacheCount || 0 >= nLocationCount || 0 >= nFriendsCount || 0 >= nGuildCount)
		{
			PROCESSLOG("MIDDLE WARE COUNT ZERO!!! Cache=%d, Location=%d, Friend=%d, Guild=%d", nCacheCount, nLocationCount, nFriendsCount, nGuildCount);
			exit(0);
		}

		bool bStart = true;
		while (true)
		{
			for (int i = 0; i < nCacheCount; i++ )
			{
				if (false == ::ProjectZ::Static::Instance()._cacheServer[i])
				{
					bStart = false;
					break;
				}
			}

			for (int i = 0; i < nLocationCount; i++ )
			{
				if (false == ::ProjectZ::Static::Instance()._locationServer[i])
				{
					bStart = false;
					break;
				}
			}

			for (int i = 0; i < nFriendsCount; i++ )
			{
				if (false == ::ProjectZ::Static::Instance()._friendServer[i])
				{
					bStart = false;
					break;
				}
			}

			for (int i = 0; i < nGuildCount; i++ )
			{
				if (false == ::ProjectZ::Static::Instance()._guildServer[i])
				{
					bStart = false;
					break;
				}
			}

			if (true == bStart)
				break;

			sleep(1);

			PROCESSLOG("WAIT CONNECTION MIDDLE WARE");
		}


		sessionList.reserve(maxConnectUserCountInServer + MAX_SESSION_POOL);

		{
			::Network::Session::Info info;
			info.eType = ::Network::Session::Info::CLIENT;
			for( int i = 0 ; i < maxConnectUserCountInServer ; i++ )
			{

				ProjectZ::Session * pSession = new ProjectZ::Session;
				LocalParser * pLocalParser = new LocalParser(pSession);

				pSession->SetNetworkInfo(info);
				pSession->_SetParser(pLocalParser);
				clsNetwork.GetSessionPool()->Post(::ProjectZ::Config::Instance().GetResourceDatabase()._port, pSession);
				sessionList.push_back(pSession);
			}

		}
		{
			::Network::Session::Info info;
			info.eType = ::Network::Session::Info::CLIENT;
			for( int i = 0 ; i < MAX_SESSION_POOL ; i++ )
			{
				ProjectZ::Session * pSession = new ProjectZ::Session;
				LocalParser * pLocalParser = new LocalParser(pSession);

				pSession->SetNetworkInfo(info);
				pSession->_SetParser(pLocalParser);
				clsNetwork.GetSessionPool()->Post(::ProjectZ::Config::Instance().GetResourceDatabase()._lobbyPort, pSession);
				sessionList.push_back(pSession);
			}
		}

		if( ::Network::Static::InstancePtr()->Listen(::ProjectZ::Config::Instance().GetResourceDatabase()._port) )
		{
			PROCESSLOG("server listen service port = %d", ::ProjectZ::Config::Instance().GetResourceDatabase()._port);
		}
		else
		{
			PROCESSLOG("server listen fail");
			bReady = false;
		}


		if( ::Network::Static::InstancePtr()->Listen(::ProjectZ::Config::Instance().GetResourceDatabase()._lobbyPort) )
		{
			PROCESSLOG("server listen lobby port = %d", ::ProjectZ::Config::Instance().GetResourceDatabase()._lobbyPort);
		}
		else
		{
			PROCESSLOG("server listen fail");
			bReady = false;
		}


		if (false == bReady)
		{
			PROCESSLOG("[SERVER START FAIL]");
			exit(0);
		}

		::ProjectZ::NLogic::Static::Instance().GETWORLDBOSSRANK()->LoadWorldBossRank();

//		::ProjectZ::User clsUser;
//		clsUser.SetUserSeq(1);
//
//		clsUser.IncreaseRefCount();
//
//		std::string json = "{\"command\":\"CPI_GIFTS\",\"gift\":[{\"value\":\"100\",\"sequence\":\"80756848\",\"name\":\"VC1\"}],\"connect_version\":\"1.0\"}";
//
//		::ProjectZ::NProcess::CPISender::Param param;
//		param._user = & clsUser;
//		param._json = json.c_str();
//
//		::ProjectZ::NProcess::Static::InstancePtr()->GetCPISender().Push(param);


		PROCESSLOG("[CHANNEL SERVER START]");
		::Logger::GETLOG()->ToAll(::Logger::OBSERVER, "[CHANNEL SERVER START]");

		std::unique_lock<std::mutex> lock(mutex_);
		condition_.wait(lock);

		End_ServerAddr();

		printf("CLOSE ALL SESSIONS...\n");
		std::for_each(sessionList.begin(), sessionList.end(), Close_ptr<ProjectZ::Session*>);
		sleep(1);

		printf("RELEASE SESSION FINALIZE...\n");
		std::for_each(sessionList.begin(), sessionList.end(), ProjectZ::NUtil::Finalize_ptr<ProjectZ::Session*>);

		printf("RELEASE SESSION...\n");
		std::for_each(sessionList.begin(), sessionList.end(), ProjectZ::NUtil::del_ptr<ProjectZ::Session*>);
	}

	::Logger::GETLOG()->ToAll(::Logger::OBSERVER, "[CHANNEL SERVER END]");

	printf("RELEASE NEWORK...\n");
	clsNetwork.End();

	printf("RELEASE TIMER...\n");
	clsTimer.End();

	printf("RELEASE DISPATCHER...\n");
	dispatcher.End();

	printf("RELEASE VALIDATOR...\n");
	validator.End();

	printf("RELEASE DB...\n");
	clsDatabase.End();

	printf("RELEASE PROXY...\n");
	::ProjectZ::NProxy::Finalize();

	printf("RELEASE LOGGER...\n");
	logger.End();

	printf("RELEASE END...\n");


	return 0;
}

